
Dans ce TP, on se focalise sur les **problématiques de performance liées à la *Data Augmentation***.
Les opérations de transformation des données d'entrée se font en général sur le CPU. On verra dans ce TP qu'il est possible de les déléguer au GPU si les ressources de calcul offertes par le CPU ne sont pas suffisantes.
Ce TP est divisé en trois parties, correspondant à trois types d'augmentation de données à implémenter :
Les cellules dans ce notebook ne sont pas prĂ©vues pour ĂȘtre modifiĂ©es, sauf rares exceptions indiquĂ©es dans les commentaires. Les TP se feront en modifiant les codes dlojz_da_X.py.
Les directives de modification seront marquées par l'étiquette TODO dans le notebook suivant.
Les solutions sont présentes dans le répertoire solutions/.
Notebook rédigé par l'équipe assistance IA de l'IDRIS, février 2024
Un module PyTorch doit avoir été chargé pour le bon fonctionnement de ce Notebook. Nécessairement, le module pytorch-gpu/py3/2.1.1 :
!module list
Currently Loaded Modulefiles: 1) cuda/11.8.0 5) openmpi/4.1.5-cuda 9) sparsehash/2.0.3 2) nccl/2.18.5-1-cuda 6) intel-mkl/2020.4 10) libjpeg-turbo/2.1.3 3) cudnn/8.7.0.84-cuda 7) magma/2.7.1-cuda 11) pytorch-gpu/py3/2.1.1 4) gcc/8.5.0(8.3.1:8.4.1) 8) sox/14.4.2 >
Les fonctions python de gestion de queue SLURM dévelopées par l'IDRIS et les fonctions dédiées à la formation DLO-JZ sont à importer.
Le module d'environnement pour les jobs et la taille des images sont fixés pour ce notebook.
TODO : choisir un pseudonyme (maximum 5 caractÚres) pour vous différencier dans la queue SLURM et dans les outils collaboratifs pendant la formation et la compétition.
from idr_pytools import display_slurm_queue, gpu_jobs_submitter, search_log
from dlojz_tools import controle_technique, compare, GPU_underthehood, plot_accuracy, lrfind_plot, imagenet_starter, turbo_profiler
MODULE = 'pytorch-gpu/py3/2.1.1'
account = 'for@a100'
name = 'pseudo' ## Pseudonyme Ă choisir
Pour afficher vos jobs dans la queue SLURM :
display_slurm_queue(name)
Remarque: Cette fonction est utilisĂ©e plusieurs fois dans ce notebook. Elle permet d'afficher la queue de maniĂšre dynamique, rafraichie toutes les 5 secondes. Elle ne s'arrĂȘte que lorsque la queue est vide. Si vous dĂ©sirez reprendre la main sur le notebook, il vous suffira d'arrĂȘter manuellement la cellule avec le bouton stop. Cela a bien sĂ»r aucun impact les jobs soumis.
Si vous voulez retirer TOUS vos jobs de la queue SLURM, décommenter et exécuter la cellule suivante :
#!scancel -u $USER
Si vous voulez retirer UN de vos jobs de la queue SLURM, décommenter, compléter et exécuter la cellule suivante :
#!scancel <jobid>
Cette partie debug permet d'afficher les fichiers de sortie et les fichiers d'erreur du job.
Il est nécessaire dans la cellule suivante d'indiquer le jobid correspondant sous le format donné.
Remarque : dans ce notebook, lorsque vous soumettrez un job, vous recevrez en retour le numĂ©ro du job dans le format suivant : jobid = ['123456']. La cellule ci-dessous peut ainsi ĂȘtre facilement actualisĂ©e.
#jobid = ['2088207']
Fichier de sortie :
%cat {search_log(contains=jobid[0])[0]}
Fichier d'erreur :
%cat {search_log(contains=jobid[0], with_err=True)['stderr'][0]}
Pour comparer son code avec les solutions mises à disposition, la fonction suivante permet d'afficher une page html contenant un différentiel de fichiers texte.
s1 = "dlojz_da_2.py"
s2 = "./solutions/dlojz_da_2.py"
s1 = "mixup.py"
s2="solutions/mixup-solution.py"
compare(s1, s2)
Voir le résultat du différentiel de fichiers sur la page suivante (attention au spoil !) :
On fixe le batch size et la taille d'image pour ce TP.
bs_optim = 512
image_size = 224
Le but de ce TP est d'ajouter la transformation RandAugment (disponible dans torchvision) dans la liste des transformations pour la Data Augmentation et de mesurer son impact sur la performance du code.
Voir la documentation torchvision sur RandAugment.
Vous pouvez exécuter les cellules suivantes pour observer l'effet de la transformation RandAugment :
import os
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models
import torch
import numpy as np
import matplotlib.pyplot as plt
transform = transforms.Compose([
transforms.RandomResizedCrop(image_size), # Random resize - Data Augmentation
transforms.RandomHorizontalFlip(), # Horizontal Flip - Data Augmentation
transforms.RandAugment(5, 9), # Random Augmentation 5: n operations, 9 : magnitude
transforms.ToTensor() # convert the PIL Image to a tensor
])
train_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet',
transform=transform)
train_dataset
Dataset ImageNet
Number of datapoints: 1281167
Root location: /gpfsscratch/idris/sos/commun/imagenet
Split: train
StandardTransform
Transform: Compose(
RandomResizedCrop(size=(224, 224), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear, antialias=warn)
RandomHorizontalFlip(p=0.5)
RandAugment(num_ops=5, magnitude=9, num_magnitude_bins=31, interpolation=InterpolationMode.NEAREST, fill=None)
ToTensor()
)
%%time
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=4,
shuffle=True)
batch = next(iter(train_loader))
print('X train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[0].shape, batch[0].dtype, batch[0].element_size()*batch[0].nelement()))
print('Y train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[1].shape, batch[1].dtype, batch[1].element_size()*batch[1].nelement()))
for i in range(4):
img = batch[0][i].numpy().transpose((1,2,0))
plt.imshow(img)
plt.axis('off')
plt.show()
X train batch, shape: torch.Size([4, 3, 224, 224]), data type: torch.float32, Memory usage: 2408448 bytes Y train batch, shape: torch.Size([4]), data type: torch.int64, Memory usage: 32 bytes
CPU times: user 2.7 s, sys: 377 ms, total: 3.08 s Wall time: 3.14 s
TODO : dans le script dlojz_da_1.py :
RandAugment dans la liste des transformations des images pour le training avec le paramétrage suivant : Nombre d'opérations = 5, Magnitude = 9.Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là .
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
command = f'dlojz_da_1.py -b {bs_optim} --image-size {image_size} --test'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task Submitted batch job 887754 jobid = ['887754']
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'éviter de relancer un job par erreur.
jobid = ['887754']
display_slurm_queue(name)
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
887754 gpu_p5 tutu ssos938 R 2:32 1 jean-zay-iam05
Done!
controle_technique(jobid)
Train throughput: 2079.58 images/second GPU throughput: 2430.28 images/second epoch time: 616.25 seconds ----------- training step time average (fwd/bkwd on GPU): 0.210675 sec (8.9%/99.2%) +/- 0.074190 loading step time average (IO + CPU to GPU transfer): 0.035528 sec +/- 0.241765
turbo_profiler(jobid)
>>> Turbo Profiler >>> Training complete in 48.483087 s

Le but de ce TP est de :
mixup et mesurer son impact sur la performance du code ;La transformation mixup n'est pas disponible dans torchvision, la fonction est disponible dans le script mixup.py. On notera que cette transformation impacte Ă la fois l'image et le label.
On choisira, comme cela est fait habituellement, de mixer 2 images présentes dans le batch généré par le DataLoader. Donc cette transformation sera faite dans la boucle d'apprentissage aprÚs génération du batch et aprÚs toute autre transformation liée à la Data Augmentation.
Vous pouvez exécuter les cellules suivantes pour observer l'effet de la transformation mixup :
import os
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models
import torch
import numpy as np
import matplotlib.pyplot as plt
transform = transforms.Compose([
transforms.RandomResizedCrop(image_size), # Random resize - Data Augmentation
transforms.RandomHorizontalFlip(), # Horizontal Flip - Data Augmentation
transforms.ToTensor() # convert the PIL Image to a tensor
])
train_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet',
transform=transform)
train_dataset
Dataset ImageNet
Number of datapoints: 1281167
Root location: /gpfsscratch/idris/sos/commun/imagenet
Split: train
StandardTransform
Transform: Compose(
RandomResizedCrop(size=(224, 224), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear, antialias=warn)
RandomHorizontalFlip(p=0.5)
ToTensor()
)
from mixup import mixup_data
%%time
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=16,
shuffle=True)
batch = next(iter(train_loader))
print('X train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[0].shape, batch[0].dtype, batch[0].element_size()*batch[0].nelement()))
print('Y train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[1].shape, batch[1].dtype, batch[1].element_size()*batch[1].nelement()))
imgs, targets = batch
imgs, targets = mixup_data(imgs, targets, num_classes=1000, alpha=2) ## Transformation mixup
for i in range(4):
img = imgs[i].numpy().transpose((1,2,0))
plt.imshow(img)
plt.axis('off')
plt.show()
print(f'target : {torch.max(targets, dim=1)[1][i]}, lambda : {torch.max(targets, dim=1)[0][i]}')
X train batch, shape: torch.Size([16, 3, 224, 224]), data type: torch.float32, Memory usage: 9633792 bytes Y train batch, shape: torch.Size([16]), data type: torch.int64, Memory usage: 128 bytes
target : 932, lambda : 0.6358286738395691
target : 760, lambda : 0.5654819011688232
target : 888, lambda : 0.8834490180015564
target : 24, lambda : 0.7696910500526428 CPU times: user 7.45 s, sys: 262 ms, total: 7.71 s Wall time: 7.93 s
ParamĂštre alpha pour la beta distribution
Dans le script mixup.py, la variable _lambda correspond Ă la proportion conservĂ©e de la premiĂšre image. Elle est choisie alĂ©atoirement suivant une distribution bĂȘta dĂ©finie sur [0, 1].
Le paramĂštre alpha agit sur la forme de la distribution bĂȘta. alpha = 1 correspond Ă une distribution uniforme, alpha < 1 favorise un tirage au sort de valeurs proches des bornes 0. ou 1., et alpha > 1 favorise un tirage au sort de valeurs proches du centre 0.5.
for alpha in [0.5, 1., 2.]:
plt.hist(np.random.beta(alpha, alpha, 1000000), bins=50, density=True, histtype='step')
plt.title(f'alpha={alpha}')
plt.show()
TODO : dans le script dlojz_da_2.py :
mixupfrom mixup import mixup_data
mixup dans la boucle d'apprentissage avant d'envoyer le batch d'images et de labels au GPU, avec le paramétrage : num_classes=1000, alpha=2.Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là .
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
command = f'dlojz_da_2.py -b {bs_optim} --image-size {image_size} --test'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task Submitted batch job 887894 jobid = ['887894']
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'éviter de relancer un job par erreur.
jobid = ['887894']
display_slurm_queue(name)
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
887894 gpu_p5 tutu ssos938 R 2:38 1 jean-zay-iam33
Done!
controle_technique(jobid)
Train throughput: 1107.83 images/second GPU throughput: 2524.46 images/second epoch time: 1156.80 seconds ----------- training step time average (fwd/bkwd on GPU): 0.202816 sec (6.1%/110.0%) +/- 0.084701 loading step time average (IO + CPU to GPU transfer): 0.259348 sec +/- 0.078801
turbo_profiler(jobid)
>>> Turbo Profiler >>> Training complete in 50.497971 s
TODO : dans le script dlojz_da_2.py :
mixup dans la boucle d'apprentissage aprÚs avoir envoyé le batch d'images et de labels au GPU, avec le paramétrage : num_classes=1000, alpha=2, device=gpu.TODO : dans le script mixup.py :
device=device à chaque fois que l'on crée un nouveau Tensor pour qu'il soit stocké en mémoire au bon emplacement (CPU ou GPU).Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là .
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
command = f'dlojz_da_2.py -b {bs_optim} --image-size {image_size} --test'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task Submitted batch job 887909 jobid = ['887909']
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'éviter de relancer un job par erreur.
jobid = ['887909']
display_slurm_queue(name)
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
887909 gpu_p5 tutu ssos938 R 2:11 1 jean-zay-iam16
Done!
controle_technique(jobid)
Train throughput: 2188.81 images/second GPU throughput: 2521.39 images/second epoch time: 585.49 seconds ----------- training step time average (fwd/bkwd on GPU): 0.203062 sec (8.6%/110.8%) +/- 0.097770 loading step time average (IO + CPU to GPU transfer): 0.030855 sec +/- 0.063996
turbo_profiler(jobid)
>>> Turbo Profiler >>> Training complete in 44.058701 s

Le but de ce TP est de :
CutMix et mesurer son impact sur la performance du code ;CutMix au calcul GPU.La transformation CutMix n'est pas disponible dans torchvision, la fonction est disponible dans le script cutmix.py. On notera que cette transformation impacte Ă la fois l'image et le label.
On choisira, comme cela est fait habituellement, de mixer 2 images présentes dans le batch généré par le dataloader. Donc cette transformation sera faite dans la boucle d'apprentissage aprÚs génération du batch et donc aprÚs toute autre transformation liée à la Data Augmentation.
Dans le script cutmix.py, la variable _lambda correspond à la proportion conservée de la premiÚre image. Elle est choisie aléatoirement suivant une distribution uniforme définie sur [0, 1].
import os
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models
import torch
import numpy as np
import matplotlib.pyplot as plt
transform = transforms.Compose([
transforms.RandomResizedCrop(image_size), # Random resize - Data Augmentation
transforms.RandomHorizontalFlip(), # Horizontal Flip - Data Augmentation
transforms.ToTensor() # convert the PIL Image to a tensor
])
train_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet',
transform=transform)
train_dataset
Dataset ImageNet
Number of datapoints: 1281167
Root location: /gpfsscratch/idris/sos/commun/imagenet
Split: train
StandardTransform
Transform: Compose(
RandomResizedCrop(size=(224, 224), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear, antialias=warn)
RandomHorizontalFlip(p=0.5)
ToTensor()
)
from cutmix import cutmix_data
%%time
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=16,
shuffle=True)
batch = next(iter(train_loader))
print('X train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[0].shape, batch[0].dtype, batch[0].element_size()*batch[0].nelement()))
print('Y train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[1].shape, batch[1].dtype, batch[1].element_size()*batch[1].nelement()))
imgs, targets = batch
imgs, targets = cutmix_data(imgs, targets, num_classes=1000)
for i in range(4):
img = imgs[i].numpy().transpose((1,2,0))
plt.imshow(img)
plt.axis('off')
plt.show()
print(f'target : {torch.max(targets, dim=1)[1][i]}, lambda : {torch.max(targets, dim=1)[0][i]}')
X train batch, shape: torch.Size([16, 3, 224, 224]), data type: torch.float32, Memory usage: 9633792 bytes Y train batch, shape: torch.Size([16]), data type: torch.int64, Memory usage: 128 bytes
target : 492, lambda : 0.9307438135147095
target : 64, lambda : 0.901945173740387
target : 103, lambda : 0.8318518996238708
target : 776, lambda : 1.0 CPU times: user 6.52 s, sys: 242 ms, total: 6.76 s Wall time: 6.96 s
TODO : dans le script dlojz_da_3.py :
CutMixfrom cutmix import cutmix_data
CutMix dans la boucle d'apprentissage aprÚs avoir envoyé le batch d'images et de labels au GPU, avec le paramétrage : num_classes=1000, device=gpu.Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là .
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
command = f'dlojz_da_3.py -b {bs_optim} --image-size {image_size} --test'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
account=account, time_max='00:10:00', qos='qos_gpu-dev')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task Submitted batch job 887951 jobid = ['887951']
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'éviter de relancer un job par erreur.
jobid = ['887951']
display_slurm_queue(name)
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
887951 gpu_p5 tutu ssos938 CG 2:33 1 jean-zay-iam22
Done!
controle_technique(jobid)
Train throughput: 1759.63 images/second GPU throughput: 2512.19 images/second epoch time: 728.30 seconds ----------- training step time average (fwd/bkwd on GPU): 0.203807 sec (7.0%/118.0%) +/- 0.104394 loading step time average (IO + CPU to GPU transfer): 0.087163 sec +/- 0.036166
turbo_profiler(jobid)
>>> Turbo Profiler >>> Training complete in 48.079175 s
Le code précédent traite les images du batch une par une, de maniÚre séquentielle (boucle for) :
mixed_x = x
for i in range(len(mixed_x)): # loop over images
mixed_x[i,:,x1[i]:x2[i],y1[i]:y2[i]] = x[s_index[i],:,x1[i]:x2[i],y1[i]:y2[i]]
Le but de cette partie est d'optimiser le code de CutMix en générant davantage de parallélisme pour profiter du GPU. Il s'agit de supprimer la boucle for et de manipuler directement des batches de tenseurs.
Le travail va porter sur la définition de deux batches de masques mask_int et mask_ext de taille [batch_size,n_channels,weight,height] que l'on appliquera de la maniÚre suivante :
mixed_x = mask_ext * x + mask_int * x[s_index, :]
La fonction à implémenter est la suivante :
def cut_mask(x1, x2, y1, y2, batch_size, W, H, device=None):
### TODO
mask_ext, mask_int = None, None
return mask_ext, mask_int
Arguments :
x1 : vecteur de longueur batch_size avec la coordonnée min dans la largeur pour chaque image du batchx2 : vecteur de longueur batch_size avec la coordonnée max dans la largeur pour chaque image du batchy1 : vecteur de longueur batch_size avec la coordonnée min dans la hauteur pour chaque image du batchy2 : vecteur de longueur batch_size avec la coordonnée max dans la hauteur pour chaque image du batchbatch_size : nombre d'images dans le batchW : largeur des imagesH : hauteur des imagesdevice : unité de calcul ('cpu' ou 'gpu')Retours:
mask_ext : tenseur de taille [batch_size,n_channels,weight,height] contenant la valeur False ou 0 Ă l'intĂ©rieur de la fenĂȘtre et True ou 1 Ă l'extĂ©rieurmask_int : tenseur de taille [batch_size,n_channels,weight,height] contenant la valeur True ou 1 Ă l'intĂ©rieur de la fenĂȘtre et False ou 0 Ă l'extĂ©rieurCrĂ©ation d'un batch de masques
Dans un premier temps, pour comprendre la procédure, nous travaillerons avec un batch de 3 images de taille 32x32.
import torch
import numpy as np
import matplotlib.pyplot as plt
batch_size = 3
W = 32
H = 32
En entrĂ©e, on connait les coordonnĂ©es des coins de la fenĂȘtre pour chaque image du batch (voir illustration ci-dessous).
# coordonnee min dans la largeur pour chaque image du batch
x1 = torch.Tensor([10, 5, 23]).int()
# coordonne max dans la largeur pour chaque image du batch
x2 = torch.Tensor([20, 25, 31]).int()
# coordonnee min dans la hauteur pour chaque image du batch
y1 = torch.Tensor([5, 10, 0]).int()
# coordonne max dans la hauteur pour chaque image du batch
y2 = torch.Tensor([10, 22, 20]).int()

1. Création de w_int et h_int
Pour construire mask_int, on va d'abord créer un batch de vecteurs ligne "largeur" w_int et un batch de vecteurs colonne "hauteur" de masques h_int (voir illustration ci-dessus).
Variables utiles : batch_size, W, H, x1, x2, y1, y2.
Voir la documentation PyTorch pour la manipulation de tenseurs : documentation torch.
Résultats attendus : (voir illustration ci-dessous)
w_int, tenseur de taille [3, 1, 32] contenant des True ou 1 si x1 ⩜ x ⩜ x2, False ou 0 sinonh_int, tenseur de taille [3, 32, 1] contenant des True ou 1 si y1 ⩜ y ⩜ y2, False ou 0 sinon
![w_int torch.Size([3, 1, 32]) / h_int torch.Size([3, 32, 1]) résultat](images/resultat_cutmix.png)
Nous avons trouvé 2 solutions pour résoudre ce problÚme.
En utilisant la fonction torch.logical_and : il s'agit d'initialiser des tenseurs Ă [x, x=0,...,31] (respectivement [y, y=0,...,31]) et d'utiliser torch.logical_and() pour appliquer les conditions x1 ⩜ x and x ⩜ x2 (respectivement y1 ⩜ y and y ⩜ y2). Le rĂ©sultat est un tenseur contenant des opĂ©rateurs logiques True et False.
En utilisant la fonction torch.cumsum : les tenseurs sont initialisés à 0, on donne la valeur 1 au x1Úme élément (respectivement y1Úme), la valeur -1 au x2+1Úme élément (respectivement y2+1Úme), puis on utilise la fonction torch.cumsum pour faire la somme cumulée des éléments. On obtient un tenseur contenant des 0 et des 1..
Dans tous les cas, il faudra jouer avec les dimensions des tenseurs. Les fonctions utiles sont : torch.arange(), .size(), .repeat(), .view(), .unsqueeze(), .zeros(), ...
Il existe certainement d'autres solutions.
Rappel : ne jamais utiliser de for nulle part !
# initialisation du tenseur w_int avec les valeurs [0,...,31]
w_int = torch.arange(W).repeat(batch_size,1,1) # batch de vecteurs ligne
# Returns the mask applying ((x1 ⩜ x) and (x ⩜ x2))
w_int = torch.logical_and(w_int >= x1.view(-1,1,1), w_int <= x2.view(-1,1,1)) # vecteurs ligne
# assert w_int has the correct size
assert w_int.size() == torch.Size([3,1,32])
for wx in w_int:
plt.imshow(wx)
plt.colorbar()
plt.show()
# initialisation du tenseur h_int avec les valeurs [0,...,31]
h_int = torch.arange(H).repeat(batch_size,1).unsqueeze(2) # batch de vecteurs colonne
# Returns the mask applying ((y1 ⩜ y) and (y ⩜ y2))
h_int = torch.logical_and(h_int >= y1.view(-1,1,1), h_int <= y2.view(-1,1,1)) # vecteurs colonne
# assert h_int has the correct size
assert h_int.size() == torch.Size([3,32,1])
for hx in h_int:
plt.imshow(hx)
plt.colorbar()
plt.show()
torch.logical_and# initialisation du tenseur w_int avec les valeurs [0,...,31]
w_int = torch.arange(W).repeat(batch_size,1,1) # batch de vecteurs ligne
# Returns the mask applying ((x1 ⩜ x) and (x ⩜ x2))
w_int = torch.logical_and(w_int >= x1.view(-1,1,1), w_int <= x2.view(-1,1,1)) # vecteurs ligne
# initialisation du tenseur h_int avec les valeurs [0,...,31]
h_int = torch.arange(H).repeat(batch_size,1).unsqueeze(2) # batch de vecteurs colonne
# Returns the mask applying ((y1 ⩜ y) and (y ⩜ y2))
h_int = torch.logical_and(h_int >= y1.view(-1,1,1), h_int <= y2.view(-1,1,1)) # vecteurs colonne
torch.cumsumOn initialise les éléments correspondant aux coordonnées x1 et y1 à 1.
On initialise les éléments correspondant aux coordonnées x2+1 et y2+1 à -1.
Puis on utilise la fonction torch.cumsum pour remplir chaque intervalle [x1,x2] et [y1,y2] de 1, le reste de 0.
Remarque : il y a une petite erreur dans cette solution qui n'a pas d'impact majeur. +1 sur votre appréciation finale si vous la trouvez !!
w_int = torch.zeros(batch_size,1,W) # vecteurs ligne
w_int[range(batch_size),0,x1] = 1.
w_int[range(batch_size),0,x2+1] = -1.
w_int = torch.cumsum(w_int, dim=2).bool() # vecteurs ligne
h_int = torch.zeros(batch_size,H,1) # vecteurs colonne
h_int[range(batch_size),y1,0] = 1.
h_int[range(batch_size),y2+1,0] = -1.
h_int = torch.cumsum(h_int, dim=1).bool() # vecteurs colonne
x2=31 ou y2=31# if x2==31, set w_int(.,0,31)=0, otherwize set w_int(.,0,x2+1)=-1
w_int[.,0,torch.minimum(torch.tensor([31]).repeat(batch_size),x2+1)]=torch.maximum(torch.tensor([-1.]).repeat(batch_size),x2-31)
# if y2==31, set h_int(.,31,0)=0, otherwize set h_int(.,y2+1,0)=-1
h_int[.,torch.minimum(torch.tensor([31]).repeat(batch_size),y2+1,0)]=torch.maximum(torch.tensor([-1.]).repeat(batch_size),y2-31)
2. Création des batches de masques intérieurs et extérieurs
# multiplication des vecteurs colonne "hauteur" h_int par les vecteurs ligne "largeur" w_int
mask_int = h_int*w_int
# visualisation des masques intérieurs pour chaque image du batch
for m in mask_int:
plt.imshow(m)
plt.colorbar()
plt.show()
# multiplication des vecteurs colonne "hauteur" h_int par les vecteurs ligne "largeur" w_int
mask_int = h_int*w_int
Par exemple en utilisant la fonction torch.logical_not.
# les masques extérieurs sont les complémentaires des masques intérieurs
mask_ext = torch.logical_not(mask_int)
# visualisation des masques extérieurs
for m in mask_ext:
plt.imshow(m)
plt.colorbar()
plt.show()
# les masques extérieurs sont les complémentaires des masques intérieurs
mask_ext = torch.logical_not(mask_int)
Implémentation de la fonction de création d'un batch de masques
Maintenant, l'idée est d'implémenter ce qui a été fait dans les cellules précédentes dans une fontion générique, en ajoutant un choix sur le device d'exécution.
TODO : implémenter la fonction de création des masques dans la cellule suivante. Les entrées de la fonction sont :
x1, x2, y1, y2,batch_size,W de l'image,H de l'image,device de calcul.Important : Pour les images RGB (channel de 3), il faut rajouter une dimension en deuxiĂšme position dans les masques finaux (doc .unsqueeze()) :
# rajouter une dimension en 2e position pour pouvoir traiter des images RGB
mask_int = mask_int.unsqueeze(1)
mask_ext = mask_ext.unsqueeze(1)
Attention : Ne pas oublier le paramÚtre device=device à chaque création d'un nouveau Tensor. Par exemple pour :
w_int = torch.zeros(batch_size,1,W,device=device)
def cut_mask(x1, x2, y1, y2, batch_size, W, H, device=None):
mask_ext, mask_int = None, None
# initialisation du tenseur w_int avec les valeurs [0,...,31]
w_int = torch.arange(W, device=device).repeat(batch_size,1,1) # batch de vecteurs ligne
# Returns the mask applying ((x1 ⩜ x) and (x ⩜ x2))
w_int = torch.logical_and(w_int >= x1.view(-1,1,1), w_int <= x2.view(-1,1,1)) # vecteurs ligne
# initialisation du tenseur h_int avec les valeurs [0,...,31]
h_int = torch.arange(H, device=device).repeat(batch_size,1).unsqueeze(2) # batch de vecteurs colonne
# Returns the mask applying ((y1 ⩜ y) and (y ⩜ y2))
h_int = torch.logical_and(h_int >= y1.view(-1,1,1), h_int <= y2.view(-1,1,1)) # vecteurs colonne
# multiplication des vecteurs colonne "hauteur" h_int par les vecteurs ligne "largeur" w_int
mask_int = h_int*w_int
# les masques extérieurs sont les complémentaires des masques intérieurs
mask_ext = torch.logical_not(mask_int)
# rajouter une dimension en 2e position pour pouvoir traiter des images RGB
mask_int = mask_int.unsqueeze(1)
mask_ext = mask_ext.unsqueeze(1)
return mask_ext, mask_int
%%time
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=16,
shuffle=True)
batch = next(iter(train_loader))
print('X train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[0].shape, batch[0].dtype, batch[0].element_size()*batch[0].nelement()))
print('Y train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[1].shape, batch[1].dtype, batch[1].element_size()*batch[1].nelement()))
imgs, targets = batch
X train batch, shape: torch.Size([16, 3, 224, 224]), data type: torch.float32, Memory usage: 9633792 bytes Y train batch, shape: torch.Size([16]), data type: torch.int64, Memory usage: 128 bytes CPU times: user 5.03 s, sys: 57.8 ms, total: 5.09 s Wall time: 5.59 s
batch_size = 16
W = image_size
H = image_size
lam = torch.rand(batch_size)
s_index = torch.randperm(batch_size) # Shuffle index
rand_x = torch.randint(W, (batch_size,))
rand_y = torch.randint(H, (batch_size,))
cut_rat = torch.sqrt(1. - lam) ## cut ratio according to the random lambda
x1 = torch.clip(rand_x - rand_x / 2, min=0).long()
x2 = torch.clip(rand_x + rand_x / 2, max=W-1).long()
y1 = torch.clip(rand_y - rand_y / 2, min=0).long()
y2 = torch.clip(rand_y + rand_y / 2, max=H-1).long()
mask_ext, mask_int = cut_mask(x1, x2, y1, y2, batch_size, W, H)
# vĂ©rifier si le masque intĂ©rieur et l'image ont le mĂȘme nombre de dimensions
try:
assert imgs.dim() == mask_int.dim()
print('OK!')
except:
print(f'Mismatch: \n dim imgs = {imgs.dim()} \n dim mask int = {mask_int.dim()} ')
OK!
# vĂ©rifier si le masque extĂ©rieur et l'image ont le mĂȘme nombre de dimensions
try:
assert imgs.dim() == mask_ext.dim()
print('OK!')
except:
print(f'Mismatch: \n dim imgs = {imgs.dim()} \n dim mask ext = {mask_ext.dim()} ')
OK!
imgs = mask_ext * imgs + mask_int * imgs[s_index, :]
for i in range(4):
img = imgs[i].numpy().transpose((1,2,0))
plt.imshow(img)
plt.axis('off')
plt.show()
Soumission du job. Attention vous sollicitez les noeuds de calcul Ă ce moment-lĂ .
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
command = f'dlojz_da_3.py -b {bs_optim} --image-size {image_size} --test'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task Submitted batch job 888142 jobid = ['888142']
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'éviter de relancer un job par erreur.
jobid = ['888142']
display_slurm_queue(name)
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
888142 gpu_p5 tutu ssos938 R 2:22 1 jean-zay-iam05
Done!
controle_technique(jobid)
Train throughput: 2266.13 images/second GPU throughput: 2567.43 images/second epoch time: 565.52 seconds ----------- training step time average (fwd/bkwd on GPU): 0.199421 sec (6.7%/105.6%) +/- 0.077219 loading step time average (IO + CPU to GPU transfer): 0.026515 sec +/- 0.049210
turbo_profiler(jobid)
>>> Turbo Profiler >>> Training complete in 42.835168 s
